#pragma once

#include "imgui/custom/Editor.h"
#include "imgui/node/Node.h"
#include "imgui/node/Pin.h"
#include "imgui/node/LinkIndex.h"
#include <unordered_map>
#include <map>
#include <vector>
#include <memory>
#include "imgui/imgui.h"

#define IMNODE_FAILED(x) if(!x) return false
#define IMNODE_SUCCESS(x) if(x) return true

namespace ImNodes
{
    struct Panel :public ImGui::Editor
    {
        ImNodes::Colors m_Colors[32];

        template<typename NodeType>
        using NodeMap = std::unordered_map<int, std::shared_ptr<NodeType>>;

        template<typename PinType>
        using PinMap = std::unordered_map<int, std::shared_ptr<PinType>>;

        template<typename LinkType>
        using LinkMap = std::unordered_map<int, std::shared_ptr<LinkType>>;

        int m_NodeIndex;

        int m_PinIndex;

        int m_LinkIndex;

        Panel() : m_NodeIndex(0), m_PinIndex(0), m_LinkIndex(0) {

            InitColors();
        }

        NodeMap<ImNodes::Node> m_Nodes;

        PinMap<ImNodes::Pin> m_Pins;

        LinkMap<ImNodes::LinkIndex> m_Links;

        // Other pins;
        PinMap<ImNodes::FloatPin> m_FloatPins;

        PinMap<ImNodes::StringPin> m_StringPins;

        PinMap<ImNodes::IntPin> m_IntPins;

        // Other links
        LinkMap<ImNodes::LinkTemp<ImNodes::FloatPin>> m_FloatLinks;

        LinkMap<ImNodes::LinkTemp<ImNodes::IntPin>> m_IntLinks;

        LinkMap<ImNodes::LinkTemp<ImNodes::StringPin>> m_StringLinks;

        bool SetNodeEnable(int nodeId, bool enable)
        {
            auto it = m_Nodes.find(nodeId);
            if (it != m_Nodes.end())
            {
                it->second->m_Enable = enable;
                return true;
            }
            return false;
        };
        bool SetLinkEnable(int linkId, bool enable)
        {
            auto it = m_Links.find(linkId);
            if (it != m_Links.end())
            {
                it->second->m_Enable = enable;
                return true;
            }
            return false;
        }
        bool HasLink(const int linkId)
        {
            return m_Links.find(linkId) != m_Links.end();
        };

        bool HasPin(const int  pinId)
        {
            return m_Pins.find(pinId) != m_Pins.end();
        };

        bool LinkRelatedNode(int linkId, int nodeId)
        {
            if (!HasLink(linkId))
            {
                return false;
            }
            auto link = m_Links[linkId];

            auto startPin = m_Pins[link->m_StartId];
            auto endPin = m_Pins[link->m_EndId];

            return (startPin->m_NodeId == nodeId || endPin->m_NodeId == nodeId);
        }


        template <typename NodeType>
        void EditNode(NodeType &t);

        template <typename PinType, typename DataType>
        void EditInputPin(PinType &pin)
        {
            ImNodes::BeginInputAttribute(pin.m_Id);
            ImGui::SetNextItemWidth(pin.m_Width);
            EditInst<DataType>(pin.m_Label, pin.m_Value);
            ImNodes::EndInputAttribute();
        }

        template <typename PinType, typename DataType>
        void EditOutputPin(PinType &pin)
        {
            ImNodes::BeginOutputAttribute(pin.m_Id);
            ImGui::SetNextItemWidth(pin.m_Width);
            EditInst<DataType>(pin.m_Label, pin.m_Value);
            ImNodes::EndOutputAttribute();
        }

        template<typename PinType>
        bool HasPin(PinMap<PinType> pins, int startId, int endId)
        {
            auto it1 = pins.find(startId);
            auto it2 = pins.find(endId);

            return (it1 != pins.end() && it2 != pins.end());
        }

        template<typename PinType>
        bool TryLink(PinMap<PinType>& pins, LinkMap<ImNodes::LinkTemp<PinType>>& links,int startId, int endId)
        {
            using LinkType = ImNodes::LinkTemp<PinType>;

            bool createLinkFlag = HasPin<PinType>(pins, startId, endId);
            if (createLinkFlag)
            {
                std::shared_ptr<LinkType> linkIndex = std::make_shared<ImNodes::LinkTemp<PinType>>(m_LinkIndex, pins.find(startId)->second, pins.find(endId)->second);

                links[linkIndex->m_Id] = linkIndex;
                m_Links[linkIndex->m_Id] = linkIndex;

            }
            return createLinkFlag;
        }

        bool CreateLink(int startId, int endId);

        template <typename NodeType>
        void TickNodes(NodeMap<NodeType> nodes)
        {
            for (auto it : nodes)
            {
                auto node = it.second;
                node->Tick();
            }
        };
        
        template<typename PinType>
        void RegisterPin(std::shared_ptr<PinType> pin);

        template<typename PinType>
        void InstLinks(LinkMap<ImNodes::LinkTemp<PinType>>& links)
        {
            //InstLink<FloatPin>(m_FloatLinks);
            for (auto it : links)
            {
                auto link = it.second;
                if (link->m_Enable)
                {
                    ImNodes::Link(link->m_Id, link->m_StartPin->m_Id, link->m_EndPin->m_Id);
                    link->Tick();
                }
            }
        }
        void InstAllLinks()
        {

            InstLinks<ImNodes::FloatPin>(m_FloatLinks);
            InstLinks<ImNodes::StringPin>(m_StringLinks);
            InstLinks<ImNodes::IntPin>(m_IntLinks);
        };

        void PushTitleColor(const ImNodes::Colors& myColors)
        {
            ImNodes::PushColorStyle(ImNodesCol_TitleBar, myColors.color);
            ImNodes::PushColorStyle(ImNodesCol_TitleBarHovered, myColors.hoverColor);
            ImNodes::PushColorStyle(ImNodesCol_TitleBarSelected, myColors.selectedColor);
        }
        void PushTitleColorIndex(const unsigned int index)
        {
            PushTitleColor(m_Colors[index]);
        }
        void PopTitleColor()
        {
            ImNodes::PopColorStyle();
            ImNodes::PopColorStyle();
            ImNodes::PopColorStyle();
        }
        template <typename NodeType>
        std::shared_ptr<NodeType> CreateRegisterNode(NodeMap<NodeType> &customNodes, ImVec2 pos)
        {
            auto node = std::make_shared<NodeType>(m_NodeIndex, m_PinIndex);
            node->m_Pos = pos;

            customNodes[node->m_Id] = node;

            m_Nodes[node->m_Id] = node;

            RegisterNodePins<NodeType>(node);
            return node;
        };

        template <typename NodeType>
        void RegisterNodePins(std::shared_ptr<NodeType> node);

        void OnCreateLink();

        template<typename T>
        void EditNodes(NodeMap<T>& ts)
        {
            for (auto it : ts)
            {
                auto node = it.second;
                if (node->m_Enable)
                {
                    EditNode<T>(*node);
                }
            }
        }
        void EditNodeFirstFrame()
        {
            for (auto it : m_Nodes)
            {
                auto node = it.second;
                if (node->m_Enable && node->m_FirstFrame)
                {
                    ImNodes::SetNodeScreenSpacePos(node->m_Id, node->m_Pos);
                    node->m_FirstFrame = false;
                }
            }
        };

        void OnDelete();

        void Tick();

        void InitColors();

    protected:
        virtual void EditAllNodes() = 0;

        virtual void OnContextWindow() = 0;

        virtual void TickAllNodes() = 0;

    };
};
